#pragma once

// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include <iostream>
#include <string>
#include "Selectable.h"
#include "UniformVar.h"
#include "ShaderManager.h"
#include "gl.h"
class ShaderManager;
class Shader;

// ----------------------------------------------------------------------------
// Class "ShaderProgram"
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------


class ShaderProgram : public Selectable
{
public:

    ShaderProgram(); 
    ~ShaderProgram();

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void addVertexShader(const std::string& fileName);
    void addFragmentShader(const std::string& fileName);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    void addVertexShader(std::istream& is);
    void addFragmentShader(std::istream& is);
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Shader* getVertexShader() const;
    Shader* getFragmentShader() const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    // Called by the manager when the shader is registered
    // with it. Thanks to this function, the relation is two-way
    void setManager(ShaderManager* sm) {  m_manager = sm; }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    int getId() const { return (int)m_id; }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void activate();
    void deactivate();

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void setUniformVar(const std::string& name, const UniformVarParams& var);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    bool hasVertexShader() const;
    bool hasFragmentShader() const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    size_t getNumUniformVars() const;
    UniformVar* getUniformVar(int i) const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    bool links() const;

private:

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    // Convenience function to save subclasses from writing some
    // errorprone boilerplate. "Shader" and "UniformVar" are friends so 
    // they can notify too
    void notifyEvent(ShaderEvent& event_);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Called by child shaders when their code change, creating the
    // need for a relink of the whole program
    friend class Shader;
    friend class UniformVar;
    void invalidateLink();

    void _init();

    mutable bool m_linkValid;
    void _link() const;
    
    bool _isActive() const;

    void _error(const char* msg) const;
    
    void _addShader(const std::string& fileName, GLenum type);
    void _addShader(std::istream& is, GLenum type, 
                    const std::string* fn=NULL);

    GLuint m_id;

    // NULL until the shader is registered with a manager
    ShaderManager* m_manager;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    ref<Shader> m_vertexShader; 
    ref<Shader> m_fragmentShader;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    std::vector<ref<UniformVar> > getUniformVars();

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    typedef std::map<std::string, ref<UniformVar> > UniformVarMap;
    UniformVarMap m_uniformVars;
    std::vector<UniformVar*> m_uniformVarVec;
};


// ----------------------------------------------------------------------------

class ShaderProgramException : public std::exception
{
public:

    ShaderProgramException(const char* msg=NULL)
        : std::exception(msg)
    {
    }
};

